﻿using System;
using System.Collections.Generic;
using ParserGenerator.Extensions;
using ParserGenerator.Output;

namespace ParserGenerator.ScannerConstruction
{
    [Serializable]
    public sealed class ScannerAutomatonGraphvizWriter
    {
        public IndentedWriter Writer { get; }

        public ScannerAutomatonGraphvizWriter(IndentedWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            Writer = writer;
        }

        private void WriteGraphvizHeader()
        {
            Writer.Write("digraph G\n");
            Writer.Write("{\n");
            Writer.OpenBlock();
            Writer.Write("rankdir = LR;\n");
            Writer.Write("graph [ dpi = 300 ];\n");
        }

        private void WriteGraphvizBody(ScannerAutomaton automaton)
        {
            if (automaton == null)
                throw new ArgumentNullException(nameof(automaton));
            var groups = automaton.Starts.GetGroups();
            WriteScannerAutomatonStates(automaton, groups);
            WriteScannerAutomatonLinks(automaton, groups);
            Writer.CloseBlock();
            Writer.Write("}");
        }

        private void WriteScannerAutomatonStates(ScannerAutomaton automaton, Dictionary<ScannerAutomatonState, HashSet<string>> groups)
        {
            if (automaton == null)
                throw new ArgumentNullException(nameof(automaton));
            if (groups == null)
                throw new ArgumentNullException(nameof(groups));
            int number = 0;
            foreach (var t in groups)
                WriteScannerAugomatonStartGroup(number++, t.Value);
            foreach (ScannerAutomatonState state in automaton.States)
                WriteScannerAutomatonState(state);
        }

        private void WriteScannerAugomatonStartGroup(int number, HashSet<string> starts)
        {
            if (starts == null)
                throw new ArgumentNullException(nameof(starts));
            Writer.Write("\n{0}\n", ~number);
            Writer.Write("[\n");
            Writer.OpenBlock();
            Writer.Write("shape = none\n");
            Writer.Write("label = <<table border=\"0\" cellspacing=\"0\">\n");
            Writer.OpenBlock();
            Writer.OpenBlock();
            Writer.OpenBlock();
            foreach (string item in starts)
                Writer.Write("<tr><td border=\"1\">{0}</td></tr>\n", item.AsHtmlText());
            Writer.CloseBlock();
            Writer.Write("</table>>\n");
            Writer.CloseBlock();
            Writer.CloseBlock();
            Writer.CloseBlock();
            Writer.Write("];\n");
        }

        private void WriteScannerAutomatonState(ScannerAutomatonState state)
        {
            if (state == null)
                throw new ArgumentNullException(nameof(state));
            string shape = state.Reductions.Count > 0 ? "doublecircle" : "circle";
            Writer.Write("\n{0} [ shape = {1} ];", state, shape);
        }

        private void WriteScannerAutomatonLinks(ScannerAutomaton automaton, Dictionary<ScannerAutomatonState, HashSet<string>> groups)
        {
            if (automaton == null)
                throw new ArgumentNullException(nameof(automaton));
            Writer.Write("\n\n");
            int number = 0;
            foreach (var t in groups)
                Writer.Write("{0} -> {1} [ color = brown ];\n", ~number++, t.Key);
            foreach (ScannerAutomatonState state in automaton.States)
            {
                foreach (var t in state.Transitions)
                {
                    Writer.Write("{0} -> {1} [ label = <", state, t.Value.Target);
                    Writer.Write(t.Key.ToString().AsHtmlText());
                    Writer.Write("> ];\n");
                }
            }
        }

        public void WriteScannerAutomaton(ScannerAutomaton automaton)
        {
            if (automaton == null)
                throw new ArgumentNullException(nameof(automaton));
            WriteGraphvizHeader();
            WriteGraphvizBody(automaton);
        }
    }
}
